/* ************************************************************************ */
/*                                                                          */
/*  Copyright (c)2005 Russell Weir | damonsbane _at_ gmail                  */
/*                                                                          */
/* This library is free software; you can redistribute it and/or            */
/* modify it under the terms of the GNU Lesser General Public               */
/* License as published by the Free Software Foundation; either             */
/* version 3.0 of the License, or (at your option) any later version.       */
/*                                                                          */
/* This library is distributed in the hope that it will be useful,          */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of           */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU        */
/* Lesser General Public License or the LICENSE file for more details.      */
/*                                                                          */
/* ************************************************************************ */

package altdate;
import altdate.AltDate.TimeStruct;

class Gregorian extends AltDate{
#if PARSER_BUILD
	public static var default_timezone	: Int = 0;
	public var timezone			: Int;
#else true
	public static var default_timezone	: tz.Zone = new tz.zones.etc.UTC();
	public var timezone(default,setTimezone): tz.Zone;
#end

	public var year(default,setYear) 	: Int;
	public var month(default,setMonth)	: Int; // 0-11
	public var day(default,setDay)		: Int; // 1-31
	public var hours(default,setHours) 	: Int;
	public var minutes(default,setMinutes)	: Int;
	public var seconds(default,setSeconds) 	: Int;
	public var value(getValue,setValue)	: Null<Float>;

	public var offset(default,null)		: Int; // seconds
	public var save(default,null)		: Int; // seconds daylight savings
	public var tzabbrev(default,null)	: String;

	public function new() {
		super();
		if(default_timezone == null)
#if !PARSER_BUILD
			default_timezone = new tz.zones.etc.UTC();
#else true
			default_timezone = 0;
#end
		// have to avoid calling the setters
		Reflect.setField(this,"year", 0);
		Reflect.setField(this,"month", 0);
		Reflect.setField(this,"day",1);
		Reflect.setField(this,"hours",0);
		Reflect.setField(this,"minutes",0);
		Reflect.setField(this,"seconds",0);
		Reflect.setField(this,"value",null);
		Reflect.setField(this,"timezone",default_timezone);
		Reflect.setField(this,"offset", 0);
		Reflect.setField(this,"save",0);
#if PARSER_BUILD
		Reflect.setField(this,"tzabbrev","UTC");
#else true
		Reflect.setField(this,"tzabbrev",timezone.getAbbrev()[0]);
#end
	}

#if PARSER_BUILD
	public function set(asDateTime:Bool, tzone:Null<Int>, year:Int, month:Int, day:Int,?hour:Null<Int>,?min:Null<Int>,?sec:Null<Int>) {
		timezone = 0;
#else true

	public function set(asDateTime:Bool, tzone:tz.Zone, year:Int, month:Int, day:Int,?hour:Int,?min:Int,?sec:Int) {

#end
		is_date_time = asDateTime;

		Reflect.setField(this,"year", year);
		Reflect.setField(this,"month", month);
		Reflect.setField(this,"day",day);
		Reflect.setField(this,"hours",if(hour == null) 0 else hour);
		Reflect.setField(this,"minutes",if(min == null) 0 else min);
		Reflect.setField(this,"seconds",if(sec == null) 0 else sec);
		Reflect.setField(this,"value",null);
		normalize();
		if(tzone == null)
			Reflect.setField(this,"timezone",default_timezone);
		else
			Reflect.setField(this,"timezone",tzone);
#if !PARSER_BUILD
		timezone.localToUtc(this);
#end
	}


	public function getYear() : Int { return year; }
	/** For haxe.Date compatability */
	public function getFullYear() : Int { return year; }
	public function getMonth() : Int { return month; }
	public function getDay() : Int { return day; }
	/** For haxe.Date compatability */
	public function getDate() : Int { return day; }
	public function getHours() : Int { return hours; }
	public function getMinutes() : Int { return minutes; }
	public function getSeconds() : Int { return seconds; }
	public function getValue() : Float {
		if(value == null)
			recalcValue();
		return value;
	}
#if !PARSER_BUILD
	public function getTimezone() : tz.Zone { return timezone; }
#end

	public function setYear( v : Int ) : Int {
		year = v;
		Reflect.setField(this,"value",null);
		normalize();
		return v;
	}
	public function setMonth( v : Int ) : Int {
		month = v;
		Reflect.setField(this,"value",null);
		normalize();
		return v;
	}
	/** For haxe.Date compatability */
	public function setDate( v : Int ) : Int {
		return setDay(v);
	}
	public function setDay( v : Int ) : Int {
		day = v;
		Reflect.setField(this,"value",null);
		normalize();
		return v;
	}
	public function setHours( v : Int ) : Int {
		hours = v;
		Reflect.setField(this,"value",null);
		normalize();
		return v;
	}
	public function setMinutes( v : Int ) : Int {
		minutes = v;
		Reflect.setField(this,"value",null);
		normalize();
		return v;
	}
	public function setSeconds( v : Int ) : Int {
		seconds = v;
		Reflect.setField(this,"value",null);
		normalize();
		return v;
	}
	public function setValue(v : Float) : Float {
		//trace(here.methodName + " current val "+Tools.floatToString(value)+
		//	" new value "+ Tools.floatToString(v));
		value = v;
#if !PARSER_BUILD
		timezone.applyJdn(this,v);
#end
		return v;
	}

#if PARSER_BUILD
	public function setTimezone(z : Int) : Int {
		timezone = z;
		return z;
	}
#else true
	public function setTimezone(z : tz.Zone) : tz.Zone {
		//trace(here.methodName);
		//trace(Tools.floatToString(value));
		if(z.name == timezone.name)
			return z;
		if(value == null)
			recalcValue();
		//trace(Tools.floatToString(value));
		timezone.remove(this);
		timezone = z;
		if(timezone.name != "Etc/UTC") {
			timezone.applyJdn(this, value);
		}
		return z;
	}
#end

	/**
		Create a JulianDay object from this Gregorian.
		The JulianDay number is available in a Gregorian
		class as Gregorian.value, but that is only the
		actual floating point JulianDay number. This method
		returns a JulianDay class.
	*/
	public function toJulianDay() : JulianDay {
		return new JulianDay(is_date_time, value);
	}

	/**
		Return a new Gregorian that truncates the Time portion
		of this one, and sets the new object to Date only
	*/
	public function toDate() : Gregorian {
		var gd = new Gregorian();
		Reflect.setField(gd,"is_date_time",false);
		Reflect.setField(gd,"year",year);
		Reflect.setField(gd,"month",month);
		Reflect.setField(gd,"day",day);
		Reflect.setField(gd,"hours",0);
		Reflect.setField(gd,"minutes",0);
		Reflect.setField(gd,"seconds",0);
		Reflect.setField(gd,"value",null);
		Reflect.setField(gd,"timezone",timezone);
		Reflect.setField(gd,"offset",offset);
		Reflect.setField(gd,"save",save);
		Reflect.setField(gd,"tzabbrev",tzabbrev);
		return gd;
	}

	/**
		Return a new Gregorian Date and Time value. If the current
		object is a Date only, the resulting DateTime will be set
		to 0:0:0 hours;minutes;seconds
	*/
	public function toDateTime() : Gregorian {
		var gd : Gregorian = new Gregorian();
		Reflect.setField(gd,"is_date_time",true);
		Reflect.setField(gd,"year",year);
		Reflect.setField(gd,"month",month);
		Reflect.setField(gd,"day",day);
		if(is_date_time) {
			Reflect.setField(gd,"hours",hours);
			Reflect.setField(gd,"minutes",minutes);
			Reflect.setField(gd,"seconds",seconds);
			Reflect.setField(gd,"value",value);
		}
		else {
			Reflect.setField(gd,"hours",0);
			Reflect.setField(gd,"minutes",0);
			Reflect.setField(gd,"seconds",0);
			Reflect.setField(gd,"value",null);
		}
		Reflect.setField(gd,"timezone",timezone);
		Reflect.setField(gd,"offset",offset);
		Reflect.setField(gd,"save",save);
		Reflect.setField(gd,"tzabbrev",tzabbrev);
		return gd;
	}

	public function toString() : String {
		var sb = new StringBuf();
		sb.add(year);
		sb.add("-");
		if(month < 9)
			sb.add("0");
		sb.add(month+1);
		sb.add("-");
		if(day < 10)
			sb.add("0");
		sb.add(day);
		if(is_date_time) {
			sb.add(" ");
			if(hours < 10)
				sb.add("0");
			sb.add(hours);
			sb.add(":");
			if(minutes < 10)
				sb.add("0");
			sb.add(minutes);
			sb.add(":");
			if(seconds < 10)
				sb.add("0");
			sb.add(seconds);
		}
		return sb.toString();
	}

	/*
	// TODO CONVERSION
	//
	/**
		Return the timestamp of the date. It will only have
		per-second resolution. This timestamp can NOT be used
		to create an equivalent haxe Date class, since the
		timestamps in it are adjusted by zone offset. This returns
		the actual seconds since the Unix Epoch (1970-01-01 00:00:00)
		in UTC
	*/
	public function getTime() : Float {
		// unix epoch = JDN 2440587.5
		// Jan 1, 2038 = 2465424.5
		//if(value < 2440587.5 || value > 2465424.5)
		//	return null;
		var v = value;
		if(v >= 2440587.5)
			v = (v - 2440587.5) * 86400;
		else
			v = 0-((2440587.5 - v) * 86400);
		return Math.round(v);
	}
	/*
	public static function fromTime(t : Float) : Gregorian {
		var gd = new Gregorian();
		gd.set(true,new tz.zones.UTC(),0,0,0);
		gd = gd.toDateTime();
		// Since my offset is GMT-6 right now, the
		// unix epoch needs to be lowered by 0.25.
		// this is where this fails
		gd.value = 2440587.25 + (t/86400000);
		return gd;
	}
	*/

/*
	function initOffset() {
		var d = Date.now();
		var offString = DateTools.format(Date.now(), "%z");   // "0400" "-0600"

		var add : Bool = true;
		var hours : Int = 0;
		var mins : Int = 0;
		var r : EReg = ~/([\-\+]*)([0-1][0-9])([0-6][0-9])$/;
		try {
			r.match(offString);
			add = { if (r.matched(1) == "-") true; else false; } ;
			hours = Std.parseInt(r.matched(2));
			mins = Std.parseInt(r.matched(3));
		} catch (e : Dynamic) { trace(e); };

		var t : Float = ((hours * 3600) + (mins * 60)) * 1000;
		if(!add) {
			l2gmt_add = t;
			gmt2l_add = 0 - t;
		}
		else {
			l2gmt_add = 0 - t;
			gmt2l_add = t;
		}
	}
*/



	///////////////////////////////////////////////////////////////////////
	//                     Internal methods                              //
	///////////////////////////////////////////////////////////////////////
	// Correct for overflows in date values (hours, days, minutes, whatever)
	// by making Jan 32 into Feb 1, for example.
	// This does not use, and has no bearing with, the underlying JDN.
	function normalize() : Void {
		//trace(here.methodName + " seconds: " +this.seconds);
		var ts : TimeStruct = JulianDay.convertToTimeStruct(
			JulianDay.convertToFloat(
			{
				year: this.year,
				month: this.month,
				day: this.day,
				hours: this.hours,
				minutes: this.minutes,
				seconds: this.seconds
			}
			)
		);
		Reflect.setField(this,"year",ts.year);
		Reflect.setField(this,"month",ts.month);
		Reflect.setField(this,"day",ts.day);
		if(is_date_time) {
			Reflect.setField(this,"hours",ts.hours);
			Reflect.setField(this,"minutes",ts.minutes);
			Reflect.setField(this,"seconds",ts.seconds);
		}
		else {
			Reflect.setField(this,"hours",0);
			Reflect.setField(this,"minutes",0);
			Reflect.setField(this,"seconds",0);
		}
	}

	function recalcValue() {
		//trace(here.methodName + " recalc jdn");
		var jd = JulianDay.fromGregorian(this);
		Reflect.setField(this,"value",jd.value);
	}

	///////////////////////////////////////////////////////////////////////
	//                     Static methods                                //
	///////////////////////////////////////////////////////////////////////

#if !PARSER_BUILD
	public static function setDefaultTimezone(z : tz.Zone) : tz.Zone {
		default_timezone = z;
		return z;
	}

	public static function getDefaultTimezone() : tz.Zone {
		return default_timezone;
	}
#end
}

